﻿using System;
using System.Linq;
using XmlStudio.DataLayer;
using XmlStudio.Enums;
using XmlStudio.Interfaces;
using XmlStudio.Models.Events;
using XmlStudio.Utilities;

namespace XmlStudio.Models.Actions {
    /// <summary>
    /// Adds child node to the specified node.
    /// </summary>
    internal sealed class AddChildNodeAction : XmlModelAction {
        #region Fields
        #region Private
        /// <summary>
        /// <see cref="BigXmlNode"/> where to add the child node.
        /// </summary>
        private BigXmlNodeInternal parentNode;

        /// <summary>
        /// <see cref="BigXmlNode"/> being added as a child node.
        /// </summary>
        private BigXmlNodeInternal childNode;

        /// <summary>
        /// Insert mode.
        /// </summary>
        private InsertMode mode;

        /// <summary>
        /// Reference child node.
        /// </summary>
        private BigXmlNodeInternal refChild;
        #endregion
        #endregion

        #region Constructors
        #region Public
        /// <summary>
        /// Initializes a new instance of the <see cref="AddChildNodeAction"/> class.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <param name="parentNode">Parent node where to add the child node.</param>
        /// <param name="childNode">Child node to add.</param>
        /// <param name="mode">Insert mode.</param>
        /// <param name="refChild">Reference node for inserting.</param>
        public AddChildNodeAction(IXmlModelWithNotifications model, BigXmlNodeInternal parentNode, BigXmlNodeInternal childNode, InsertMode mode, BigXmlNodeInternal refChild)
            : base(model) {
            CheckArguments(parentNode, childNode, mode, refChild);

            this.parentNode = parentNode;
            this.wasModified = parentNode != null ? parentNode.Modified : childNode.Modified;
            this.childNode = childNode;
            this.mode = mode;
            this.refChild = refChild;
            //this.address = this.parentNode.Address.DeepClone<int[]>();
        }
        #endregion
        #endregion

        #region Methods
        #region Static
        private static void CheckArguments(BigXmlNodeInternal parentNode, BigXmlNodeInternal childNode, InsertMode mode, BigXmlNodeInternal refChild) {
            //if(parentNode == null) {
            //    throw new ArgumentNullException("parentNode cannot be null");
            //}

            if(childNode == null) {
                throw new ArgumentNullException("childNode cannot be null");
            }

            if(mode == InsertMode.After || mode == InsertMode.Before) {
                if(refChild == null) {
                    throw new ArgumentNullException("refChild cannot be null using current InserMode");
                }
            }
        }
        #endregion

        #region Protected
        /// <summary>
        /// To do: you can only AddChild to the fully expanded node!!!
        /// </summary>
        protected override void ExecuteCore() {

            if(parentNode != null) {
                var lParentNode = this.parentNode.XmlNode;
                var lChildNode = this.childNode.XmlNode;

                if(this.parentNode.NodeState != ExpandState.Expanded) {
                    throw new InvalidOperationException("Cannot add child nodes on nodes that are not fully expanded.");
                }

                switch(this.mode) {
                    case InsertMode.First: {
                            this.parentNode.ChildNodes.Insert(0, this.childNode);
                            this.childNode.SetParent(this.parentNode);

                            TreeBuilder.AdjustAddresses(this.parentNode, this.model);

                            if(lParentNode.HasChildNodes) {
                                lParentNode.InsertBefore(lChildNode, lParentNode.FirstChild);
                            } else {
                                lParentNode.AppendChild(lChildNode);
                            }

                            this.parentNode.SetModified(true);
                            this.childNode.SetModified(true);
                            this.model.TotalNodeCount++;
                            break;
                        }
                    case InsertMode.After: {
                            this.parentNode.ChildNodes.Insert(this.refChild.Address.Last(), this.childNode);
                            lParentNode.InsertAfter(lChildNode, this.refChild.XmlNode);
                            this.childNode.SetParent(this.parentNode);

                            TreeBuilder.AdjustAddresses(this.parentNode, this.model);

                            this.parentNode.SetModified(true);
                            this.childNode.SetModified(true);
                            this.model.TotalNodeCount++;
                            break;
                        }
                    case InsertMode.Before: {
                            this.parentNode.ChildNodes.Insert(this.refChild.Address.Last() - 1, this.childNode);
                            lParentNode.InsertBefore(lChildNode, this.refChild.XmlNode);
                            this.childNode.SetParent(this.parentNode);

                            TreeBuilder.AdjustAddresses(this.parentNode, this.model);

                            this.parentNode.SetModified(true);
                            this.childNode.SetModified(true);
                            this.model.TotalNodeCount++;
                            break;
                        }
                    case InsertMode.Last: {
                            this.parentNode.ChildNodes.Add(this.childNode);
                            lParentNode.AppendChild(lChildNode);
                            this.childNode.SetParent(this.parentNode);

                            TreeBuilder.AdjustAddresses(this.parentNode, this.model);

                            this.parentNode.SetModified(true);
                            this.childNode.SetModified(true);
                            this.model.TotalNodeCount++;
                            break;
                        }
                }

                this.model.AddNodeWithChildren(this.childNode);
                this.model.SetModified(true);
                this.model.OnNodeAdded(new NodeAddedEventArgs<BigXmlNode>(this.parentNode, this.childNode, this.mode, this.refChild));
                this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
            } else {
                this.model.Root = this.childNode;
                this.model.AddNode(this.childNode.XmlNode, this.childNode);

                this.model.SetModified(true);
                this.model.OnRootElementAdded(new RootElementAddedEventArgs<BigXmlNode>(this.childNode));
                this.model.OnRawXmlChanged(new RawXmlChangedEventArgs());
                this.model.TotalNodeCount++;
            }
        }

        /// <summary>
        /// Performs undo of this action.
        /// </summary>
        protected override void UnExecuteCore() {
            var action = new RemoveNodeAction(this.model, this.childNode);
            action.Execute();

            if(this.parentNode != null) {
                this.parentNode.SetModified(wasModified);
            }

            this.model.SetModified(this.modelWasModified);
        }
        #endregion
        #endregion
    }
}